﻿using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Bread.Utility;

/// <summary>
/// 异步TCP客户端
/// </summary>
public class AsyncTcpClient : IDisposable
{
    #region Properties

    /// <summary>
    /// 是否已与服务器建立连接
    /// </summary>
    public bool Connected { get { return _tcpClient.Client?.Connected ?? false; } }
    /// <summary>
    /// 远端服务器的IP地址列表
    /// </summary>
    public IPAddress[] Addresses { get; private set; }
    /// <summary>
    /// 远端服务器的端口
    /// </summary>
    public int Port { get; private set; } = 0;
    /// <summary>
    /// 连接重试次数
    /// </summary>
    public int MaxRetryCount { get; set; } = 3;
    /// <summary>
    /// 尝试次数用完后标记为超时
    /// </summary>
    public bool IsTimeout { get; set; } = false;
    /// <summary>
    /// 连接重试间隔, 单位秒
    /// </summary>
    public int RetryInterval { get; set; } = 3;
    /// <summary>
    /// 远端服务器终结点
    /// </summary>
    public IPEndPoint RemoteIPEndPoint
    {
        get { return new IPEndPoint(Addresses[0], Port); }
    }
    /// <summary>
    /// 本地客户端终结点
    /// </summary>
    protected IPEndPoint? LocalIPEndPoint { get; private set; }
    /// <summary>
    /// 通信所使用的编码
    /// </summary>
    public Encoding Encoding { get; set; } = Encoding.UTF8;

    #endregion

    #region Fields

    private TcpClient _tcpClient;
    private bool _disposed = false;
    private int _retries = 0;

    #endregion

    #region Ctors

    /// <summary>
    /// 异步TCP客户端
    /// </summary>
    /// <param name="remoteEP">远端服务器终结点</param>
    public AsyncTcpClient(IPEndPoint remoteEP)
      : this(new[] { remoteEP.Address }, remoteEP.Port)
    {
    }

    /// <summary>
    /// 异步TCP客户端
    /// </summary>
    /// <param name="remoteEP">远端服务器终结点</param>
    /// <param name="localEP">本地客户端终结点</param>
    public AsyncTcpClient(IPEndPoint remoteEP, IPEndPoint localEP)
      : this(new[] { remoteEP.Address }, remoteEP.Port, localEP)
    {
    }

    /// <summary>
    /// 异步TCP客户端
    /// </summary>
    /// <param name="remoteIPAddress">远端服务器IP地址</param>
    /// <param name="remotePort">远端服务器端口</param>
    public AsyncTcpClient(IPAddress remoteIPAddress, int remotePort)
      : this(new[] { remoteIPAddress }, remotePort)
    {
    }

    /// <summary>
    /// 异步TCP客户端
    /// </summary>
    /// <param name="remoteIPAddress">远端服务器IP地址</param>
    /// <param name="remotePort">远端服务器端口</param>
    /// <param name="localEP">本地客户端终结点</param>
    public AsyncTcpClient(
      IPAddress remoteIPAddress, int remotePort, IPEndPoint localEP)
      : this(new[] { remoteIPAddress }, remotePort, localEP)
    {
    }

    /// <summary>
    /// 异步TCP客户端
    /// </summary>
    /// <param name="remoteHostName">远端服务器主机名</param>
    /// <param name="remotePort">远端服务器端口</param>
    public AsyncTcpClient(string remoteHostName, int remotePort)
      : this(Dns.GetHostAddresses(remoteHostName), remotePort)
    {
    }

    /// <summary>
    /// 异步TCP客户端
    /// </summary>
    /// <param name="remoteHostName">远端服务器主机名</param>
    /// <param name="remotePort">远端服务器端口</param>
    /// <param name="localEP">本地客户端终结点</param>
    public AsyncTcpClient(
      string remoteHostName, int remotePort, IPEndPoint localEP)
      : this(Dns.GetHostAddresses(remoteHostName), remotePort, localEP)
    {
    }

    /// <summary>
    /// 异步TCP客户端
    /// </summary>
    /// <param name="remoteIPAddresses">远端服务器IP地址列表</param>
    /// <param name="remotePort">远端服务器端口</param>
    public AsyncTcpClient(IPAddress[] remoteIPAddresses, int remotePort)
      : this(remoteIPAddresses, remotePort, null)
    {
    }

    /// <summary>
    /// 异步TCP客户端
    /// </summary>
    /// <param name="remoteIPAddresses">远端服务器IP地址列表</param>
    /// <param name="remotePort">远端服务器端口</param>
    /// <param name="localEP">本地客户端终结点</param>
    public AsyncTcpClient(
      IPAddress[] remoteIPAddresses, int remotePort, IPEndPoint? localEP)
    {
        Addresses = remoteIPAddresses;
        Port = remotePort;
        LocalIPEndPoint = localEP;

        if (LocalIPEndPoint != null) {
            _tcpClient = new TcpClient(LocalIPEndPoint);
        }
        else {
            _tcpClient = new TcpClient();
        }

    }

    #endregion
   
    #region Connect

    /// <summary>
    /// 连接到服务器
    /// </summary>
    /// <returns>异步TCP客户端</returns>
    public AsyncTcpClient Connect()
    {
        if (!Connected) {
            // start the async connect operation
            _tcpClient.BeginConnect(
              Addresses, Port, HandleTcpServerConnected, _tcpClient);
        }

        return this;
    }

    /// <summary>
    /// 关闭与服务器的连接
    /// </summary>
    /// <returns>异步TCP客户端</returns>
    public AsyncTcpClient Close()
    {
        if (Connected) {
            _retries = 0;
            _tcpClient.Close();
            _tcpClient.Dispose();
            RaiseServerDisconnected(Addresses, Port);
        }

        return this;
    }

    #endregion

    #region Receive

    private void HandleTcpServerConnected(IAsyncResult ar)
    {
        if (_disposed || IsTimeout) return;

        try {
            _tcpClient.EndConnect(ar);
            RaiseServerConnected(Addresses, Port);
            _retries = 0;
        }
        catch (Exception ex) {
           
            if (_retries > 0) {
                Log.Debug(string.Format(CultureInfo.InvariantCulture,
                  "Connect to server with retry {0} failed.", _retries));
            }

            _retries++;
            if (_retries > MaxRetryCount) {
                // we have failed to connect to all the IP Addresses,
                // connection has failed overall.
                RaiseServerExceptionOccurred(Addresses, Port, ex);
                IsTimeout = true;
                return;
            }
            else {
                Log.Debug(string.Format(CultureInfo.InvariantCulture,
                  "Waiting {0} seconds before retrying to connect to server.",
                  RetryInterval));
                Thread.Sleep(TimeSpan.FromSeconds(RetryInterval));
                Connect();
                return;
            }
        }

        // we are connected successfully and start asyn read operation.
        byte[] buffer = new byte[_tcpClient.ReceiveBufferSize];
        _tcpClient.GetStream().BeginRead(
          buffer, 0, buffer.Length, HandleDatagramReceived, buffer);
    }

    private void HandleDatagramReceived(IAsyncResult ar)
    {
        if (_disposed || IsTimeout) return;

        try {
            NetworkStream stream = _tcpClient.GetStream();

            int numberOfReadBytes = 0;
            try {
                numberOfReadBytes = stream.EndRead(ar);
            }
            catch {
                numberOfReadBytes = 0;
            }

            if (numberOfReadBytes == 0) {
                // connection has been closed
                Close();
                return;
            }

            // received byte and trigger event notification
            byte[] buffer = (byte[])ar.AsyncState!;
            byte[] receivedBytes = new byte[numberOfReadBytes];
            Buffer.BlockCopy(buffer, 0, receivedBytes, 0, numberOfReadBytes);
            RaiseDatagramReceived(_tcpClient, receivedBytes);
            RaisePlaintextReceived(_tcpClient, receivedBytes);

            // then start reading from the network again
            stream.BeginRead(
              buffer, 0, buffer.Length, HandleDatagramReceived, buffer);
        }
        catch {

        }
       
    }

    #endregion

    #region Events

    /// <summary>
    /// 接收到数据报文事件
    /// </summary>
    public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>>? DatagramReceived;
    /// <summary>
    /// 接收到数据报文明文事件
    /// </summary>
    public event EventHandler<TcpDatagramReceivedEventArgs<string>>? PlaintextReceived;

    private void RaiseDatagramReceived(TcpClient sender, byte[] datagram)
    {
        if (DatagramReceived != null) {
            DatagramReceived(this,
              new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));
        }
    }

    private void RaisePlaintextReceived(TcpClient sender, byte[] datagram)
    {
        if (PlaintextReceived != null) {
            PlaintextReceived(this,
              new TcpDatagramReceivedEventArgs<string>(
                sender, this.Encoding.GetString(datagram, 0, datagram.Length)));
        }
    }

    /// <summary>
    /// 与服务器的连接已建立事件
    /// </summary>
    public event EventHandler<TcpServerConnectedEventArgs>? ServerConnected;
    /// <summary>
    /// 与服务器的连接已断开事件
    /// </summary>
    public event EventHandler<TcpServerDisconnectedEventArgs>? ServerDisconnected;
    /// <summary>
    /// 与服务器的连接发生异常事件
    /// </summary>
    public event EventHandler<TcpServerExceptionOccurredEventArgs>? ServerExceptionOccurred;

    private void RaiseServerConnected(IPAddress[] ipAddresses, int port)
    {
        if (ServerConnected != null) {
            ServerConnected(this,
              new TcpServerConnectedEventArgs(ipAddresses, port));
        }
    }

    private void RaiseServerDisconnected(IPAddress[] ipAddresses, int port)
    {
        if (ServerDisconnected != null) {
            ServerDisconnected(this,
              new TcpServerDisconnectedEventArgs(ipAddresses, port));
        }
    }

    private void RaiseServerExceptionOccurred(
      IPAddress[] ipAddresses, int port, Exception innerException)
    {
        if (ServerExceptionOccurred != null) {
            ServerExceptionOccurred(this,
              new TcpServerExceptionOccurredEventArgs(
                ipAddresses, port, innerException));
        }
    }

    #endregion

    #region Send

    /// <summary>
    /// 发送报文
    /// </summary>
    /// <param name="datagram">报文</param>
    public void Send(byte[] datagram)
    {
        if (datagram == null)
            throw new ArgumentNullException("datagram");

        if (_disposed || IsTimeout) return;

        if (!Connected) {
            RaiseServerDisconnected(Addresses, Port);
            throw new InvalidProgramException(
              "This client has not connected to server.");
        }

        _tcpClient.GetStream().BeginWrite(
          datagram, 0, datagram.Length, HandleDatagramWritten, _tcpClient);
    }

    private void HandleDatagramWritten(IAsyncResult ar)
    {
        if (_disposed || IsTimeout) return;

        var client = ar.AsyncState as TcpClient;
        if (client == null) return;
        client.GetStream().EndWrite(ar);
    }

    /// <summary>
    /// 发送报文
    /// </summary>
    /// <param name="datagram">报文</param>
    public void Send(string datagram)
    {
        if (_disposed || IsTimeout) return;

        Send(Encoding.GetBytes(datagram));
    }

    #endregion

    #region IDisposable Members

    /// <summary>
    /// Performs application-defined tasks associated with freeing,
    /// releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Releases unmanaged and - optionally - managed resources
    /// </summary>
    /// <param name="disposing"><c>true</c> to release both managed
    /// and unmanaged resources; <c>false</c>
    /// to release only unmanaged resources.
    /// </param>
    protected virtual void Dispose(bool disposing)
    {
        if (!this._disposed) {
            if (disposing) {
                try {
                    Close();
                }
                catch (SocketException ex) {
                    Log.Exception(ex);
                }
            }

            _disposed = true;
        }
    }

    #endregion
}



/// <summary>
/// 与服务器的连接发生异常事件参数
/// </summary>
public class TcpServerExceptionOccurredEventArgs : EventArgs
{
    /// <summary>
    /// 与服务器的连接发生异常事件参数
    /// </summary>
    /// <param name="ipAddresses">服务器IP地址列表</param>
    /// <param name="port">服务器端口</param>
    /// <param name="innerException">内部异常</param>
    public TcpServerExceptionOccurredEventArgs(
      IPAddress[] ipAddresses, int port, Exception innerException)
    {
        if (ipAddresses == null)
            throw new ArgumentNullException("ipAddresses");

        this.Addresses = ipAddresses;
        this.Port = port;
        this.Exception = innerException;
    }

    /// <summary>
    /// 服务器IP地址列表
    /// </summary>
    public IPAddress[] Addresses { get; private set; }
    /// <summary>
    /// 服务器端口
    /// </summary>
    public int Port { get; private set; }
    /// <summary>
    /// 内部异常
    /// </summary>
    public Exception Exception { get; private set; }

    /// <summary>
    /// Returns a <see cref="System.String"/> that represents this instance.
    /// </summary>
    /// <returns>
    /// A <see cref="System.String"/> that represents this instance.
    /// </returns>
    public override string ToString()
    {
        string s = string.Empty;
        foreach (var item in Addresses) {
            s = s + item.ToString() + ',';
        }
        s = s.TrimEnd(',');
        s = s + ":" + Port.ToString(CultureInfo.InvariantCulture);

        return s;
    }
}

/// <summary>
/// 与服务器的连接已建立事件参数
/// </summary>
public class TcpServerConnectedEventArgs : EventArgs
{
    /// <summary>
    /// 与服务器的连接已建立事件参数
    /// </summary>
    /// <param name="ipAddresses">服务器IP地址列表</param>
    /// <param name="port">服务器端口</param>
    public TcpServerConnectedEventArgs(IPAddress[] ipAddresses, int port)
    {
        if (ipAddresses == null)
            throw new ArgumentNullException("ipAddresses");

        this.Addresses = ipAddresses;
        this.Port = port;
    }

    /// <summary>
    /// 服务器IP地址列表
    /// </summary>
    public IPAddress[] Addresses { get; private set; }
    /// <summary>
    /// 服务器端口
    /// </summary>
    public int Port { get; private set; }

    /// <summary>
    /// Returns a <see cref="System.String"/> that represents this instance.
    /// </summary>
    /// <returns>
    /// A <see cref="System.String"/> that represents this instance.
    /// </returns>
    public override string ToString()
    {
        string s = string.Empty;
        foreach (var item in Addresses) {
            s = s + item.ToString() + ',';
        }
        s = s.TrimEnd(',');
        s = s + ":" + Port.ToString(CultureInfo.InvariantCulture);

        return s;
    }
}

/// <summary>
/// 与服务器的连接已断开事件参数
/// </summary>
public class TcpServerDisconnectedEventArgs : EventArgs
{
    /// <summary>
    /// 与服务器的连接已断开事件参数
    /// </summary>
    /// <param name="ipAddresses">服务器IP地址列表</param>
    /// <param name="port">服务器端口</param>
    public TcpServerDisconnectedEventArgs(IPAddress[] ipAddresses, int port)
    {
        if (ipAddresses == null)
            throw new ArgumentNullException("ipAddresses");

        this.Addresses = ipAddresses;
        this.Port = port;
    }

    /// <summary>
    /// 服务器IP地址列表
    /// </summary>
    public IPAddress[] Addresses { get; private set; }
    /// <summary>
    /// 服务器端口
    /// </summary>
    public int Port { get; private set; }

    /// <summary>
    /// Returns a <see cref="System.String"/> that represents this instance.
    /// </summary>
    /// <returns>
    /// A <see cref="System.String"/> that represents this instance.
    /// </returns>
    public override string ToString()
    {
        string s = string.Empty;
        foreach (var item in Addresses) {
            s = s + item.ToString() + ',';
        }
        s = s.TrimEnd(',');
        s = s + ":" + Port.ToString(CultureInfo.InvariantCulture);

        return s;
    }
}